import {
  clientId,
  ENV,
  DEV_DOMAIN,
  PRO_DOMAIN,
  BASE_URL,
  LOGIN_PAGE,
} from "@/config";
import {
  decryptBase64,
  decryptWithAes,
  encryptBase64,
  encryptWithAes,
  generateAesKey,
} from "@/utils/crypto";
import { decrypt, encrypt } from "@/utils/jsencrypt";
import { getToken, removeToken } from "./auth";
import type { Result } from "@/types";
import errorCode from "@/utils/errorCode";
import { HttpStatus } from "@/enums/HttpStatus";
import { isH5, transParams } from "@/utils/index";

const encryptHeader = "encrypt-key";

export const globalHeaders = () => {
  return {
    Authorization: "Bearer " + getToken(),
    clientid: clientId,
  };
};

interface RequestOptions {
  url: string;
  data?: any;
  params?: any;
  header?: any;
  method?: "GET" | "POST" | "PUT" | "DELETE";
  loading?: boolean;
}

class Request {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  private request(options: RequestOptions): Promise<any> {
    const { url, data, params, header, method, loading = true } = options;

    return new Promise((resolve, reject) => {
      if (loading) {
        uni.showLoading({
          mask: true,
        });
      }

      let newUrl = url;
      if ((method === "GET" || method === "DELETE") && params) {
        newUrl = url + "?" + transParams(params);
        newUrl = newUrl.slice(0, -1);
      }

      const isToken = header?.isToken === false;
      const isEncrypt = header?.isEncrypt === true;

      const headers = {
        ...header,
        clientId: clientId,
      };
      if (getToken() && !isToken) {
        headers["Authorization"] = "Bearer " + getToken();
      }

      let newData;
      if (isEncrypt && (method === "POST" || method === "PUT")) {
        // 生成一个 AES 密钥
        const aesKey = generateAesKey();
        headers[encryptHeader] = encrypt(encryptBase64(aesKey));
        newData =
          typeof data === "object"
            ? encryptWithAes(JSON.stringify(data), aesKey)
            : encryptWithAes(data, aesKey);
      } else {
        newData = data;
      }

      uni.request({
        url: this.baseUrl + newUrl,
        data: newData,
        header: headers,
        method: method || "GET",
        timeout: 120000,
        success: (res) => {
          if (loading) {
            uni.hideLoading();
          }

          // 二进制数据则直接返回
          if (
            res.header.responseType === "blob" ||
            res.header.responseType === "arraybuffer"
          ) {
            return res.data;
          }

          // 加密后的 AES 秘钥
          const keyStr = res.header[encryptHeader];
          // 加密
          if (
            keyStr != null &&
            keyStr != "" &&
            res.header.responseType === "string"
          ) {
            const data = res.data;
            // 请求体 AES 解密
            const base64Str = decrypt(keyStr);
            // base64 解码 得到请求头的 AES 秘钥
            const aesKey = decryptBase64(base64Str.toString());
            // aesKey 解码 data
            const decryptData = decryptWithAes(data as string, aesKey);
            // 将结果 (得到的是 JSON 字符串) 转为 JSON
            res.data = JSON.parse(decryptData);
          }

          const data: Result<any> = res.data as any;

          const code = data.code || HttpStatus.SUCCESS;
          const msg = errorCode[code] || data.msg || errorCode["default"];

          if (code === 401) {
            uni.showToast({
              title: "登录状态已过期，请重新登录",
              icon: "none",
              duration: 5000,
            });
            removeToken();
            uni.redirectTo({
              url: LOGIN_PAGE,
            });
            reject(data);
          } else if (
            code === HttpStatus.SERVER_ERROR ||
            code === HttpStatus.WARN ||
            code !== HttpStatus.SUCCESS
          ) {
            uni.showToast({
              title: msg,
              icon: "none",
              duration: 3000,
            });
            reject(data);
          } else {
            resolve(data);
          }
        },
        fail: (err) => {
          if (loading) {
            uni.hideLoading();
          }
          reject(err);
        },
      });
    });
  }

  get(
    url: string,
    params?: any,
    header?: any,
    loading: boolean = true
  ): Promise<any> {
    return this.request({ url, params, header, method: "GET", loading });
  }

  post(
    url: string,
    data?: any,
    header?: any,
    loading: boolean = true
  ): Promise<any> {
    return this.request({ url, data, header, method: "POST", loading });
  }

  put(
    url: string,
    data?: any,
    header?: any,
    loading: boolean = true
  ): Promise<any> {
    return this.request({ url, data, header, method: "PUT", loading });
  }

  delete(
    url: string,
    params?: any,
    header?: any,
    loading: boolean = true
  ): Promise<any> {
    return this.request({ url, params, header, method: "DELETE", loading });
  }

  upload(
    url: string,
    filePath: string,
    name: string,
    formData?: any,
    header?: any
  ): Promise<any> {
    return new Promise((resolve, reject) => {
      const headers = {
        ...header,
        clientId: clientId,
      };
      if (getToken()) {
        headers["Authorization"] = "Bearer " + getToken();
      }

      uni.uploadFile({
        url: this.baseUrl + url,
        filePath: filePath,
        name: name,
        formData: formData,
        header: headers,
        success: (res) => {
          if (res.statusCode === 200) {
            resolve(JSON.parse(res.data));
          } else {
            reject(new Error(res.errMsg));
          }
        },
        fail: (err) => {
          reject(err);
        },
      });
    });
  }

  download(url: string, header?: any): Promise<any> {
    return new Promise((resolve, reject) => {
      const headers = {
        ...header,
        clientId: clientId,
      };
      if (getToken()) {
        headers["Authorization"] = "Bearer " + getToken();
      }

      uni.downloadFile({
        url: this.baseUrl + url,
        header: headers,
        success: (res) => {
          if (res.statusCode === 200) {
            resolve(res.tempFilePath);
          } else {
            reject(new Error(res.errMsg));
          }
        },
        fail: (err) => {
          reject(err);
        },
      });
    });
  }
}

const baseDomain = ENV === "dev" ? DEV_DOMAIN : PRO_DOMAIN;

const baseUrl = (isH5() ? "" : baseDomain) + BASE_URL;

const request = new Request(baseUrl);

export default request;
